Pressidian
花园入口
笔记
项目
关于
实验室
GitHub
花园入口
笔记
项目
关于
实验室
GitHub

KNOWLEDGE PATHS

笔记库
当前位置
笔记库/前端/面试/面经

10 美团-一面

6 分钟阅读 · Note

目录树 578 篇

          • 0 面试技巧&HR面⬇️
          • 1磐松资产-前端实习-一面
          • 2字节跳动-豆包前端实习-全部
          • 3百度-前端实习面试-一面
          • 5光庭-前端实习-一面
          • 6懂车帝-前端实习-一面
          • 7字节-抖音团购-全部
          • 8腾讯-前端-全部
          • 9字节&美团-笔试
          • 10 美团-一面
          • 11 B站-一面
          • 12 腾讯-一面-企业微信团队
          • 13 腾讯-一面-腾讯金融科技
          • 14 腾讯-二面-腾讯金融科技
          • 15 美团-一面-核心本地商业
          • 16 斑马智行-全部
          • 17 字节-一面-飞书
          • 18 美团-二面-核心本地商业
          • 19 心影随形-全部
          • 20 thunderbit-全部
          • 21 converge ai-全部
          • 22 字节-国际化广告创意与品牌
        • 可投递企业
      • 前端技术栈
    • 笔记目录
    • CLAUDE.md
    • Vue 组件与 Render 函数

关联笔记 6

↗0 面试技巧&HR面⬇️同一路径↗11 B站-一面同一路径↗12 腾讯-一面-企业微信团队同一路径↗13 腾讯-一面-腾讯金融科技同一路径↗14 腾讯-二面-腾讯金融科技同一路径↗15 美团-一面-核心本地商业同一路径
  • 10 美团-一面

10 美团-一面

> Last Format Time:6/12/2026 21:03:57

提问

  • hooks使用的注意事项
  • content-type的值是什么样的
  • React组件的生命周期是怎么样的,如果有父子组件呢

这三个问题涵盖了 React 开发中的核心机制与网络基础,下面为你逐一详细解答:


Hooks 使用的注意事项

在使用 React Hooks 时,为了保证组件状态的正确性和避免诡异的 Bug,必须遵守以下核心原则和常见避坑点:

  • 严格遵守两条铁律:
    • 只在最顶层使用 Hook:绝对不要在循环、条件判断(if/else)或者嵌套函数中调用 Hook。React 依靠 Hooks 的调用顺序来维护组件的状态,顺序一旦被打乱,状态就会错乱。
    • 只在 React 函数组件或自定义 Hook 中调用:不要在普通的 JavaScript 函数中调用 Hook。
  • 闭包陷阱与依赖项数组:
    • 在使用 useEffect、useCallback、useMemo 时,必须准确填写依赖项数组(deps)。如果漏填依赖,回调函数内部可能会拿到过期的旧状态(闭包陷阱)。
    • 如果某些逻辑确实不需要响应依赖变化,应使用 useRef 来保存最新值。
  • 副作用的清理(Cleanup):
    • 在 useEffect 中如果开启了定时器、添加了全局事件监听或建立了 WebSocket 连接,必须在返回的清理函数中进行清除(如 clearInterval、removeEventListener),否则会导致内存泄漏。
  • React 18 严格模式下的"双次执行":
    • 在开发环境下,React 的 <StrictMode> 会故意将组件挂载两次(mount -> unmount -> remount),以检测副作用是否纯净。这会导致 useEffect 执行两次。这是正常现象,生产环境不会发生,切勿为了规避它而写出不规范的代码。
  • 保持 Render 的纯净:
    • 不要在组件的渲染主体(即 return 之前的逻辑)中直接执行 fetch 请求、setTimeout 或直接修改 DOM,这些副作用必须放在 useEffect 中。

Content-Type 的值是什么样的

Content-Type(也称为 MIME 类型)是 HTTP 协议中用于描述"实体主体"数据格式的头部字段,它告诉接收方(浏览器或服务器)如何解析这段数据。

它的标准语法格式为:类型名/子类型名 [; 可选参数]。

以下是开发中最常见的几种 Content-Type 值及其应用场景:

Content-Type 值数据形态与说明典型应用场景
application/jsonJSON 格式数据现代前后端 API 交互最常用的数据格式
application/x-www-form-urlencoded键值对(如 name=Tom&age=18)传统 HTML 表单默认提交格式,不含文件上传
multipart/form-data多段二进制数据流文件上传时必须使用此类型,需配合 boundary 参数分隔字段
text/htmlHTML 纯文本浏览器返回网页文档,通常会带 charset=utf-8 参数
application/octet-stream二进制流触发浏览器下载文件(如 zip、exe),不尝试在浏览器内打开
text/plain无格式的纯文本返回未经渲染的普通文本内容
image/jpeg, image/png图片二进制数据直接在 <img> 标签中展示图片

React 组件的生命周期(含父子组件顺序)

React 组件的生命周期主要分为三个阶段:挂载(Mounting)、更新(Updating) 和 卸载(Unmounting)。

在现代 React(React 16.3+ / React 18)的类组件中,核心生命周期方法的执行顺序如下:

  • 挂载阶段:constructor -> static getDerivedStateFromProps -> render -> componentDidMount
  • 更新阶段:static getDerivedStateFromProps -> shouldComponentUpdate -> render -> getSnapshotBeforeUpdate -> componentDidUpdate
  • 卸载阶段:componentWillUnmount

(注:函数组件通过 useEffect 来模拟这些阶段:无依赖数组相当于挂载/卸载;有依赖数组相当于挂载/更新/卸载。)

父子组件的生命周期执行顺序

当父组件中包含子组件时,生命周期遵循**"父准备 -> 子渲染 -> 子完成 -> 父完成"**的规律。

1. 初始挂载阶段:

  • 渲染阶段(由父到子):
    1. 父组件 constructor
    2. 父组件 getDerivedStateFromProps
    3. 父组件 render (解析出子组件)
    4. 子组件 constructor
    5. 子组件 getDerivedStateFromProps
    6. 子组件 render
  • 提交阶段(由子到父,DOM 挂载完成): 7. 子组件 componentDidMount 8. 父组件 componentDidMount

2. 更新阶段(以父组件 setState 触发更新为例):

  • 渲染阶段(由父到子):
    1. 父组件 getDerivedStateFromProps
    2. 父组件 shouldComponentUpdate
    3. 父组件 render
    4. 子组件 getDerivedStateFromProps
    5. 子组件 shouldComponentUpdate
    6. 子组件 render
  • 提交阶段(由子到父,DOM 更新完成): 7. 子组件 getSnapshotBeforeUpdate 8. 子组件 componentDidUpdate 9. 父组件 getSnapshotBeforeUpdate 10. 父组件 componentDidUpdate

3. 卸载阶段:

  • 如果父组件被卸载,它会先触发自己的卸载逻辑,然后触发子组件的卸载。
  • 核心顺序:父组件 componentWillUnmount -> 子组件 componentWillUnmount(注:在实际的 DOM 移除过程中,通常是子组件先被从 DOM 中摘除,但在 React 的生命周期方法调用链中,往往表现为父组件触发卸载流程,进而导致子组件执行卸载钩子)。

代码

实现一个useRequest()

在 React 中封装一个基础的 useRequest 自定义 Hook,可以帮助我们统一管理异步请求的加载状态(loading)、返回数据(data)和错误信息(error)。

下面是一个基于 React 原生 Hooks(useState, useEffect, useCallback, useRef)封装的 useRequest 实现方案:

基础版 useRequest 封装

import { useState, useEffect, useCallback, useRef } from 'react';

// 定义 useRequest 的返回状态类型
interface UseRequestState<T> {
  data: T | null;
  error: Error | null;
  loading: boolean;
}

// 定义 useRequest 的返回结果,包含状态和操作函数
interface UseRequestReturn<T, P extends any[]> extends UseRequestState<T> {
  run: (...params: P) => Promise<T | void>;
  cancel: () => void;
}

function useRequest<T, P extends any[]>(
  service: (...args: P) => Promise<T>,
  options: { manual?: boolean } = {}
): UseRequestReturn<T, P> {
  const { manual = false } = options;

  // 管理请求的状态
  const [state, setState] = useState<UseRequestState<T>>({
    data: null,
    error: null,
    loading: !manual, // 如果是自动请求,初始 loading 为 true
  });

  // 使用 useRef 存储 service,防止闭包陷阱,始终保持最新的 service 引用
  const serviceRef = useRef(service);
  serviceRef.current = service;

  // 用于取消请求的 AbortController
  const abortControllerRef = useRef<AbortController | null>(null);

  // 核心请求执行函数
  const run = useCallback(async (...params: P) => {
    // 如果上一次请求还未结束,先取消它
    if (abortControllerRef.current) {
      abortControllerRef.current.abort();
    }
    
    const controller = new AbortController();
    abortControllerRef.current = controller;

    // 开始请求,设置 loading 为 true
    setState((prev) => ({ ...prev, loading: true, error: null }));

    try {
      const result = await serviceRef.current(...params);
      
      // 请求成功,更新 data 和 loading
      if (!controller.signal.aborted) {
        setState({ data: result, error: null, loading: false });
        return result;
      }
    } catch (error) {
      // 如果是主动取消的请求,不更新错误状态
      if ((error as any).name === 'AbortError') return;

      // 请求失败,更新 error 和 loading
      if (!controller.signal.aborted) {
        setState({ data: null, error: error as Error, loading: false });
        throw error;
      }
    } finally {
      // 清理当前的 controller 引用
      if (abortControllerRef.current === controller) {
        abortControllerRef.current = null;
      }
    }
  }, []);

  // 取消请求的方法
  const cancel = useCallback(() => {
    if (abortControllerRef.current) {
      abortControllerRef.current.abort();
      abortControllerRef.current = null;
    }
    setState((prev) => ({ ...prev, loading: false }));
  }, []);

  // 如果不是手动模式,组件挂载时自动发起请求
  useEffect(() => {
    if (!manual) {
      // 这里假设自动请求不带参数,如果有默认参数可以通过 options 传入
      run(); 
    }
    // 组件卸载时取消请求
    return () => {
      cancel();
    };
  }, [manual, run, cancel]);

  return { ...state, run, cancel };
}

export default useRequest;

核心功能解析

  1. 状态管理 (useState):统一管理 loading、data、error 三个核心状态。
  2. 自动/手动触发 (options.manual):默认情况下组件挂载会自动执行请求;如果设置 manual: true,则需要调用返回的 run 方法手动触发(例如点击按钮查询)。
  3. 请求取消 (AbortController):利用浏览器原生的 AbortController 实现请求取消。如果在一次请求还未完成时发起了新的请求,或者组件卸载了,会自动取消上一次的请求,防止旧数据覆盖新数据。
  4. 解决闭包问题 (useRef):使用 useRef 存储传入的 service 异步函数,确保在请求执行时永远拿到的是最新定义的函数引用。

如何在组件中使用

import React from 'react';
import useRequest from './useRequest';

// 模拟一个异步请求函数
const fetchUserInfo = async (id: number) => {
  const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
  if (!res.ok) throw new Error('Network response was not ok');
  return res.json();
};

const UserInfo = () => {
  // 手动触发模式
  const { data, loading, error, run } = useRequest(fetchUserInfo, { manual: true });

  const handleFetch = () => {
    run(1).catch(err => console.log('请求出错:', err));
  };

  return (
    <div>
      <button onClick={handleFetch} disabled={loading}>
        {loading ? '加载中...' : '获取用户信息'}
      </button>
      
      {error && <p style={{ color: 'red' }}>请求失败: {error.message}</p>}
      
      {data && (
        <div>
          <h3>用户: {data.name}</h3>
          <p>邮箱: {data.email}</p>
        </div>
      )}
    </div>
  );
};

export default UserInfo;

进阶扩展思路

在实际的生产项目中(如参考业界成熟的 ahooks 库),useRequest 往往还会通过插件化的方式扩展更多高级功能:

  • 防抖与节流 (debounceWait / throttleWait):适用于搜索框等高频触发场景。
  • 轮询 (pollingInterval):定时自动重新发起请求,适用于实时数据监控。
  • 缓存与 SWR (cacheKey):缓存请求结果,提升用户体验。
  • 生命周期钩子 (onSuccess, onError, onFinally):方便在请求的不同阶段处理业务逻辑。

你可以根据实际业务需求,在这个基础版本上逐步添加这些功能。


拼音匹配

实现一个方法,根据拼音返回输入内容,两个参数分别是字符串数组和字符串,返回一个数组,用例如下:

  • 输入是['北京BeiJing', '上海ShangHai', '重庆ChongQing', '宝鸡BaoJi'], 'BeiJ', 要求返回['北京']
  • 输入是['北京BeiJing', '上海ShangHai', '重庆ChongQing', '宝鸡BaoJi'], 'BJ', 要求返回['北京','宝鸡']
  • 输入是['宜昌YiChang', '延安YanAn'], 'YA', 要求返回**['延安', '宜昌']**
const list = [ "北京BeiJing", "上海ShangHai", "重庆ChongQing", "宝鸡BaoJi","宜昌YiChang", "延安YanAn"];

/**
* input: string 待匹配的拼音,如"YA", "BeiJ"
**/
function match(input: string) {
  // do something
  // 返回城市名称列表
}
const list = [ "北京BeiJing", "上海ShangHai", "重庆ChongQing", "宝鸡BaoJi","宜昌YiChang", "延安YanAn"];

/**
* input: string 待匹配的拼音,如"YA", "BeiJ"
**/
function match(input = "YA") {
  if(!input) return []
  const l = []
  list.forEach((city, index)=>{
    let c = city.slice(2)
    l.push(c)
  })

  function getWeight (item) {
    let map = new Map()
    for(char of item){
      map.set(char, (map.has(char) ?? 0) + 1)
    }

    let weight = 0
    for(char of input){
      if(map.has(char)) {weight+=map.get(char)}
    }

    return weight

  }

  let weight = new Map()
  l.forEach((item, index)=>{
    weight.set(getWeight(item), item)
  })
  let res = Array.from(weight)
  return res
}

console.log(match("BeiJ"))